package jfprincesstests.systemtests;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.Map.Entry;

import laboratoryQ.junit.ignore.patch.BaseTestCase;
import jfuzzyprincepless.zarchive.algorithm.structure.ICommandConstruction;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.fuzzyfor.*;
import jfuzzyprincepless.negotiation.DefaultNegotiationContext;
import jfuzzyprincepless.negotiation.IExecutionContext;
import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.agents.BaseNegotiationAgent;
import jfuzzyprincepless.zarchive.variables.IFuzzySet;
import jfuzzyprincepless.zarchive.variables.FuzzySet;
import jfuzzyprincepless.zarchive.negotiation.ZArchNegotiationUtils;
import jfuzzyprincepless.zarchive.negotiation.services.IExecuteForService;

public class TestPremiumAssignment extends BaseTestCase {
	// TODOLONG: same test with utility function [when utility function mechanism]
	/**
	 * ### ### ###  SCHEMA  ### ### ###
	 * 	r = new R()
	 * 	FOR (e IN eset)
	 * 		v = 0
	 * 		v += c0(e)
	 * 		v += c1(e)
	 * 		v += c2(e)
	 * 		r.foo(e, v)
	 *
	 * ### ### ### SEMANTIC ### ### ###
	 * 	premiums = new Premiums()
	 * 	FOR (employee IN positivEmployees)
	 * 		premVal = 0
	 * 		premVal += educationLevel(employee)
	 * 		premVal += timeSpentAtWork(employee)
	 * 		premVal -= reprimands(employee)
	 * 		premiums.assignPremium(premVal)
	 */
	public void testPremiumAssignment() {
		PremiumsInfo premiumInfo = new PremiumsInfo();
		IFuzzySet<Employee> employeeSet = new FuzzySet<Employee>();
		employeeSet.add(new Employee("W0", 1, 2, 3), 0.3);
		employeeSet.add(new Employee("W1", 4, 5, 6), 0.4);
		employeeSet.add(new Employee("W2", 7, 8, 9), 0.5);
		employeeSet.add(new Employee("W3", 10, 11, 12), 0.6);
		employeeSet.add(new Employee("W4", 13, 14, 15), 0.7);
		NegotiationFuzzyFor<Employee> forPositivEmployeeLoop =
			new NegotiationFuzzyFor<Employee>(employeeSet);

		PremiumValVariable premiumValVariable =
			new PremiumValVariable();
		ICommandConstruction initialisePremiumVal =
			new InitialisePremiumVal(premiumValVariable);
		ICommandConstruction addEducationLevel =
			new AddEducationLevel(premiumValVariable);
		ICommandConstruction addTimeSpentLevel =
			new AddTimeSpentLevel(premiumValVariable);
		ICommandConstruction substractReprimand =
			new SubstractReprimand(premiumValVariable);
		ICommandConstruction premiumAssigner =
			new PremiumAssigner(
				premiumInfo,
				premiumValVariable
			);
		forPositivEmployeeLoop.addBodyCommands(
			initialisePremiumVal,
			addEducationLevel,
			addTimeSpentLevel,
			substractReprimand,
			premiumAssigner
		);
		forPositivEmployeeLoop.extractAgentsFromConstructions();

		forPositivEmployeeLoop.execute(new DefaultNegotiationContext());

		assertEquals(
			"W0: 4.50; W1: 12.00; W2: 22.50; W3: 36.00; W4: 52.50; ",
			premiumInfo.toString()
		);
	}

	private static class Employee {
		private String name;
		private double educationLevel;
		private double reprimandLevel;
		private double spentTime;

		private Employee(
			String name,
			double educationLevel,
			double reprimandLevel,
			double spentTime
		) {
			this.name = name;
			this.educationLevel = educationLevel;
			this.reprimandLevel = reprimandLevel;
			this.spentTime = spentTime;
		}

		public double getEducationLevel() {
			return educationLevel;
		}

		public double getReprimandLevel() {
			return reprimandLevel;
		}

		public double getSpentTime() {
			return spentTime;
		}

		@Override
		public String toString() {
			return name;
		}
	}

	private static class PremiumsInfo {
		private Map<Employee, Double> employee2premium =
			new LinkedHashMap<Employee, Double>();

		public void putPremium(
			Employee employee,
			double premium
		) {
			employee2premium.put(employee, premium);
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			DecimalFormat format = new DecimalFormat();
			format.setMinimumFractionDigits(2);
			format.setMaximumFractionDigits(2);
			DecimalFormatSymbols dfs =
				format.getDecimalFormatSymbols();
			dfs.setDecimalSeparator('.');
			format.setDecimalFormatSymbols(dfs);

			for (
				Entry<Employee,Double> entry :
				employee2premium.entrySet()
			) {
				Employee employee = entry.getKey();
				Double value = entry.getValue();
				String valueRepr = format.format(value);
				sb.append(employee).append(": ")
					.append(valueRepr).append("; ");
			}
			return sb.toString();
		}
	}

	// TODOLONG: create IFyzzyVariable
	private static class PremiumValVariable {
		private double value;

		private PremiumValVariable() {
			erase();
		}

		public void erase() {
			value = 0.0;
		}

		public void addVal(double value) {
			this.value += value;
		}

		public double getPremiumValue() {
			return value;
		}
	}

	private abstract static class BasePremiumNegotiationAgent
		extends BaseNegotiationAgent
		implements ICommandConstruction
	{
		protected PremiumValVariable premiumValVariable;
		protected double theValue = -1;
		protected boolean active = false;

		protected BasePremiumNegotiationAgent(
			PremiumValVariable premiumValVariable
		) {
			this.premiumValVariable = premiumValVariable;
		}

		@Override
		public void execute(IExecutionContext context) {
			if (!active) {
				return;
			}
			premiumValVariable.addVal(theValue);
		}

		@SuppressWarnings("unchecked")
		protected Employee emploeeFromContext(
			INegotiationContext context
		) {
			IExecuteForService<Employee> forServ =
				context.getService(IExecuteForService.class);
			Employee employee = forServ.getCurrentElem();
			return employee;
		}
	}

	private static class InitialisePremiumVal
		extends BasePremiumNegotiationAgent
	{
		protected InitialisePremiumVal(
			PremiumValVariable premiumValVariable
		) {
			super(premiumValVariable);
		}

		@Override
		public void execute(IExecutionContext context) {
			premiumValVariable.erase();
		}
	}

	private static class AddEducationLevel
		extends BasePremiumNegotiationAgent
	{
		protected double veight = 10.0;

		protected AddEducationLevel(
			PremiumValVariable premiumValVariable
		) {
			super(premiumValVariable);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double educ = employee.getEducationLevel();
			theValue = educ * veight;
			active = true;
			return false;
		}
	}

	private static class AddTimeSpentLevel
		extends BasePremiumNegotiationAgent
	{
		protected double veight = 15.0;

		protected AddTimeSpentLevel(
			PremiumValVariable premiumValVariable
		) {
			super(premiumValVariable);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double time = employee.getSpentTime();
			theValue = time * veight;
			active = true;
			return false;
		}
	}

	private static class SubstractReprimand
		extends BasePremiumNegotiationAgent
	{
		private double veight = 20.0;

		protected SubstractReprimand(
			PremiumValVariable premiumValVariable
		) {
			super(premiumValVariable);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double reprimand = employee.getReprimandLevel();
			theValue = reprimand * veight * -1;
			active = true;
			return false;
		}
	}

	private static class PremiumAssigner
		extends BasePremiumNegotiationAgent
	{
		private PremiumsInfo premiumsInfo;

		protected PremiumAssigner(
			PremiumsInfo premiumsInfo,
			PremiumValVariable premiumValVariable
		) {
			super(premiumValVariable);
			this.premiumsInfo = premiumsInfo;
		}

		@Override
		public void execute(IExecutionContext econtext) {
			INegotiationContext ncontext =
				(INegotiationContext) econtext;
			Employee employee = emploeeFromContext(ncontext);
			double membership = ZArchNegotiationUtils
				.getCurrentMembershipDegree(ncontext);
			double premiumValue = premiumValVariable.getPremiumValue();
			double premium2assign = premiumValue * membership;
			premiumsInfo.putPremium(employee, premium2assign);
		}
	}
}
